﻿using System;
using System.Reflection.Emit;

namespace Reflective
{
    /// <summary>
    /// Extension methods for <see cref="DynamicMethod"/>.
    /// </summary>
    public static class DynamicMethodExtensions
    {
        /// <summary>
        /// Completes the dynamic method and creates a delegate that can be used to execute it.
        /// </summary>
        /// <typeparam name="TDelegate">
        /// A delegate type whose signature matches that of the dynamic method.
        /// </typeparam>
        /// <param name="method">
        /// The <see cref="DynamicMethod"/> to return the delegate for.
        /// </param>
        /// <returns>
        /// A delegate of the specified type, which can be used to execute the dynamic
        /// method.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="method"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para>The dynamic method has no method body.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TDelegate"/> has the wrong number of parameters or the wrong parameter types.</para>
        /// </exception>
        public static TDelegate CreateDelegate<TDelegate>(this DynamicMethod method)
            where TDelegate : class
        {
            if (method == null)
                throw new ArgumentNullException("method");

            return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate));
        }

        /// <summary>
        /// Completes the dynamic method and creates a delegate that can be used to execute
        /// it, specifying the delegate type and an object the delegate is bound to.
        /// </summary>
        /// <typeparam name="TDelegate">
        /// A delegate type whose signature matches that of the dynamic method, minus
        /// the first parameter.
        /// </typeparam>
        /// <param name="method">
        /// The <see cref="DynamicMethod"/> to return the delegate for.
        /// </param>
        /// <param name="target">
        /// An object the delegate is bound to. Must be of the same type as the first
        /// parameter of the dynamic method.
        /// </param>
        /// <returns>
        /// A delegate of the specified type, which can be used to execute the dynamic
        /// method with the specified target object.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="method"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// <para>The dynamic method has no method body.</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <para><typeparamref name="TDelegate"/> has the wrong number of parameters or the wrong parameter types.</para>
        /// <para>- or -</para>
        /// <para><paramref name="target"/> is not the same type as the first parameter of the dynamic method,
        /// and is not assignable to that type.</para>
        /// </exception>
        public static TDelegate CreateDelegate<TDelegate>(this DynamicMethod method, object target)
            where TDelegate : class
        {
            if (method == null)
                throw new ArgumentNullException("method");
            if (target == null)
                throw new ArgumentNullException("target");

            return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate), target);
        }
    }
}